// Copyright 2015 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "content/browser/android/java/gin_java_bridge_message_filter.h"

#include "base/auto_reset.h"
#include "build/build_config.h"
#include "content/browser/android/java/gin_java_bridge_dispatcher_host.h"
#include "content/browser/android/java/java_bridge_thread.h"
#include "content/common/gin_java_bridge_messages.h"
#include "content/public/browser/render_frame_host.h"
#include "content/public/browser/render_process_host.h"
#include "content/public/browser/web_contents.h"

#if !defined(OS_ANDROID)
#error "JavaBridge only supports OS_ANDROID"
#endif

namespace {

const char kGinJavaBridgeMessageFilterKey[] = "GinJavaBridgeMessageFilter";

} // namespace

namespace content {

GinJavaBridgeMessageFilter::GinJavaBridgeMessageFilter()
    : BrowserMessageFilter(GinJavaBridgeMsgStart)
    , current_routing_id_(MSG_ROUTING_NONE)
{
}

GinJavaBridgeMessageFilter::~GinJavaBridgeMessageFilter()
{
}

void GinJavaBridgeMessageFilter::OnDestruct() const
{
    if (BrowserThread::CurrentlyOn(BrowserThread::UI)) {
        delete this;
    } else {
        BrowserThread::DeleteSoon(BrowserThread::UI, FROM_HERE, this);
    }
}

bool GinJavaBridgeMessageFilter::OnMessageReceived(
    const IPC::Message& message)
{
    DCHECK(JavaBridgeThread::CurrentlyOn());
    base::AutoReset<int32_t> routing_id(&current_routing_id_,
        message.routing_id());
    bool handled = true;
    IPC_BEGIN_MESSAGE_MAP(GinJavaBridgeMessageFilter, message)
    IPC_MESSAGE_HANDLER(GinJavaBridgeHostMsg_GetMethods, OnGetMethods)
    IPC_MESSAGE_HANDLER(GinJavaBridgeHostMsg_HasMethod, OnHasMethod)
    IPC_MESSAGE_HANDLER(GinJavaBridgeHostMsg_InvokeMethod, OnInvokeMethod)
    IPC_MESSAGE_HANDLER(GinJavaBridgeHostMsg_ObjectWrapperDeleted,
        OnObjectWrapperDeleted)
    IPC_MESSAGE_UNHANDLED(handled = false)
    IPC_END_MESSAGE_MAP()
    return handled;
}

base::TaskRunner* GinJavaBridgeMessageFilter::OverrideTaskRunnerForMessage(
    const IPC::Message& message)
{
    // As the filter is only invoked for the messages of the particular class,
    // we can return the task runner unconditionally.
    return JavaBridgeThread::GetTaskRunner();
}

void GinJavaBridgeMessageFilter::AddRoutingIdForHost(
    GinJavaBridgeDispatcherHost* host,
    RenderFrameHost* render_frame_host)
{
    base::AutoLock locker(hosts_lock_);
    hosts_[render_frame_host->GetRoutingID()] = host;
}

void GinJavaBridgeMessageFilter::RemoveHost(GinJavaBridgeDispatcherHost* host)
{
    base::AutoLock locker(hosts_lock_);
    auto iter = hosts_.begin();
    while (iter != hosts_.end()) {
        if (iter->second == host)
            hosts_.erase(iter++);
        else
            ++iter;
    }
}

// static
scoped_refptr<GinJavaBridgeMessageFilter> GinJavaBridgeMessageFilter::FromHost(
    GinJavaBridgeDispatcherHost* host, bool create_if_not_exists)
{
    RenderProcessHost* rph = host->web_contents()->GetRenderProcessHost();
    scoped_refptr<GinJavaBridgeMessageFilter> filter = base::UserDataAdapter<GinJavaBridgeMessageFilter>::Get(
        rph, kGinJavaBridgeMessageFilterKey);
    if (!filter && create_if_not_exists) {
        filter = new GinJavaBridgeMessageFilter();
        rph->AddFilter(filter.get());
        rph->SetUserData(kGinJavaBridgeMessageFilterKey,
            new base::UserDataAdapter<GinJavaBridgeMessageFilter>(
                filter.get()));
    }
    return filter;
}

GinJavaBridgeDispatcherHost* GinJavaBridgeMessageFilter::FindHost()
{
    base::AutoLock locker(hosts_lock_);
    auto iter = hosts_.find(current_routing_id_);
    if (iter != hosts_.end())
        return iter->second;
    // Not being able to find a host is OK -- we can receive messages from
    // RenderFrames for which the corresponding host part has already been
    // destroyed. That means, any references to Java objects that the host was
    // holding were already released (with the death of ContentViewCore), so we
    // can just ignore such messages.
    // RenderProcessHostImpl does the same -- if it can't find a listener
    // for the message's routing id, it just drops the message silently.
    // The only action RenderProcessHostImpl does is sending a reply to incoming
    // synchronous messages, but as we handle all our messages using
    // IPC_MESSAGE_HANDLER, the reply will be sent automatically.
    return nullptr;
}

void GinJavaBridgeMessageFilter::OnGetMethods(
    GinJavaBoundObject::ObjectID object_id,
    std::set<std::string>* returned_method_names)
{
    DCHECK(JavaBridgeThread::CurrentlyOn());
    GinJavaBridgeDispatcherHost* host = FindHost();
    if (host) {
        host->OnGetMethods(object_id, returned_method_names);
    } else {
        *returned_method_names = std::set<std::string>();
    }
}

void GinJavaBridgeMessageFilter::OnHasMethod(
    GinJavaBoundObject::ObjectID object_id,
    const std::string& method_name,
    bool* result)
{
    DCHECK(JavaBridgeThread::CurrentlyOn());
    GinJavaBridgeDispatcherHost* host = FindHost();
    if (host) {
        host->OnHasMethod(object_id, method_name, result);
    } else {
        *result = false;
    }
}

void GinJavaBridgeMessageFilter::OnInvokeMethod(
    GinJavaBoundObject::ObjectID object_id,
    const std::string& method_name,
    const base::ListValue& arguments,
    base::ListValue* wrapped_result,
    content::GinJavaBridgeError* error_code)
{
    DCHECK(JavaBridgeThread::CurrentlyOn());
    GinJavaBridgeDispatcherHost* host = FindHost();
    if (host) {
        host->OnInvokeMethod(current_routing_id_, object_id, method_name, arguments,
            wrapped_result, error_code);
    } else {
        wrapped_result->Append(base::Value::CreateNullValue());
        *error_code = kGinJavaBridgeRenderFrameDeleted;
    }
}

void GinJavaBridgeMessageFilter::OnObjectWrapperDeleted(
    GinJavaBoundObject::ObjectID object_id)
{
    DCHECK(JavaBridgeThread::CurrentlyOn());
    GinJavaBridgeDispatcherHost* host = FindHost();
    if (host)
        host->OnObjectWrapperDeleted(current_routing_id_, object_id);
}

// static
void GinJavaBridgeMessageFilter::RemoveFilter(
    GinJavaBridgeDispatcherHost* host)
{
    RenderProcessHost* rph = host->web_contents()->GetRenderProcessHost();
    rph->RemoveUserData(kGinJavaBridgeMessageFilterKey);
}

} // namespace content
